import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.util.HashSet;
import java.util.Set;

/**
 * Created with IntelliJ IDEA.
 * User: Marcin
 * Date: 02.05.13
 * Time: 19:42
 * To change this template use File | Settings | File Templates.
 */
public class WorldPanel extends JPanel implements KeyListener, MouseListener {

    private Map map;

    private Agent agent;

    private static WorldPanel INSTANCE = null;


    public static WorldPanel getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new WorldPanel();
        }
        return INSTANCE;
    }


    private int laserSize;

    private int particleSize;
    private double moveNoiseParam;
    private double rotationNoiseParam;
    private double laserNoiseParam;

    private int sigma;

    private final Set<Integer> pressed = new HashSet<Integer>();

    public WorldPanel() {
        this.laserSize = 9;
        this.particleSize = 1000;
        this.moveNoiseParam = 0.5;
        this.rotationNoiseParam = 0.5;
        this.laserNoiseParam = 0.5;
        this.sigma = 50;
        addKeyListener(this);
        addMouseListener(this);
        setMap(new Map());
        setAgent(new ExampleAgent(this.map));

    }

    public void reset() {
        this.laserSize = 9;
        this.particleSize = 1000;
        this.moveNoiseParam = 0.5;
        this.rotationNoiseParam = 0.5;
        this.laserNoiseParam = 0.5;
        this.sigma = 50;
        setMap(new Map());
        setAgent(new ExampleAgent(this.map));
    }

    public void resetAgentMap(int laserSize, int particleSize, double moveNoiseParam, double rotationNoiseParam, double laserNoiseParam, int sigma) {
        this.laserSize = laserSize;
        this.particleSize = particleSize;
        this.moveNoiseParam = moveNoiseParam;
        this.rotationNoiseParam = rotationNoiseParam;
        this.laserNoiseParam = laserNoiseParam;
        this.sigma = sigma;
        setMap(new Map());
        setAgent(new ExampleAgent(this.map));
    }

    public void addNotify() {
        super.addNotify();
        requestFocus();
    }

    public void setMap(Map map) {
        this.map = map;
        Image img = map.getImg();
        Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
        setPreferredSize(size);
        setMinimumSize(size);
        setMaximumSize(size);
        setSize(size);
        setLayout(null);
    }

    public void setAgent(Agent agent) {
        this.agent = agent;
    }

    public void paintComponent(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;
        RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.drawImage(map.getImg(), 0, 0, null);
        g2d.setRenderingHints(renderHints);


        agent.sense(getLaserSize());


        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
        for (Particle particle : agent.getParticles()) {
            g.setColor(Color.green);
            double sizeWeight = particleSize * particle.getWeight();
            if (sizeWeight < 3) {
                sizeWeight = 3;
            }
            g.fillOval(particle.getX() - (int) (sizeWeight / 2), particle.getY() - (int) (sizeWeight / 2), (int) (sizeWeight), (int) (sizeWeight));
        }


        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
        int width = 2;
        g2d.setStroke(new BasicStroke(width));
        for (Point laserPoint : agent.getLaserShoots()) {
            if (laserPoint != null) {
                g2d.setPaint(new GradientPaint(
                        map.getAgentPositionX(),
                        map.getAgentPositionY(),
                        Color.cyan,
                        laserPoint.x,
                        laserPoint.y,
                        Color.magenta));

                g.drawLine(map.getAgentPositionX(), map.getAgentPositionY(), laserPoint.x, laserPoint.y);
            }
        }

        AffineTransform at = new AffineTransform();

//        at.rotate(agent.getRotation(),map.getAgentPositionX()+(agent.getImg().getWidth(null)/2),map.getAgentPositionY()+(agent.getImg().getHeight(null)/2));
        at.rotate(agent.getRotation(), map.getAgentPositionX(), map.getAgentPositionY());
        at.translate(map.getAgentPositionX() - 10, map.getAgentPositionY() - 10);
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
        g2d.drawImage(agent.getImg(), at, null);

    }


    @Override
    public void keyTyped(KeyEvent e) {
    }

    @Override
    public void keyPressed(KeyEvent e) {
        pressed.add(e.getKeyCode());
        for (Integer keyCode : pressed) {
            switch (keyCode) {
                case KeyEvent.VK_UP:
                    //ruch na planszy
                    map.moveAgent(Direction.FORWARD, agent.getRotation());
                    //filtr czasteczkowy
                    agent.move(Direction.FORWARD, agent.getRotation(), getMoveNoiseParam(), getLaserNoiseParam(), getSigma(), getLaserSize());
                    agent.sense(getLaserSize());
                    break;
                case KeyEvent.VK_DOWN:
                    //ruch na planszy
                    map.moveAgent(Direction.BACKWARD, agent.getRotation());
                    //filtr czasteczkowy
                    agent.move(Direction.BACKWARD, agent.getRotation(), getMoveNoiseParam(), getLaserNoiseParam(), getSigma(), getLaserSize());
                    agent.sense(getLaserSize());
                    break;
                case KeyEvent.VK_LEFT:
                    //ruch na planszy  + obrót czastek
                    agent.rotate((float) -Math.toRadians(15), getRotationNoiseParam());
                    agent.sense(getLaserSize());
                    //
                    break;
                case KeyEvent.VK_RIGHT:
                    //ruch na planszy + obrót czastek
                    agent.rotate((float) Math.toRadians(15), getRotationNoiseParam());
                    agent.sense(getLaserSize());
                    break;
            }
        }
        repaint();
    }

    @Override
    public void keyReleased(KeyEvent e) {
        pressed.remove(e.getKeyCode());
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        this.addNotify();
    }

    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void mouseExited(MouseEvent e) {
        //To change body of implemented methods use File | Settings | File Templates.
    }


    public int getLaserSize() {
        return laserSize;
    }

    public void setLaserSize(int laserSize) {
        this.laserSize = laserSize;
    }


    public int getParticleSize() {
        return particleSize;
    }

    public void setParticleSize(int particleSize) {
        this.particleSize = particleSize;
    }


    public double getMoveNoiseParam() {
        return moveNoiseParam;
    }

    public void setMoveNoiseParam(double moveNoiseParam) {
        this.moveNoiseParam = moveNoiseParam;
    }

    public double getRotationNoiseParam() {
        return rotationNoiseParam;
    }

    public void setRotationNoiseParam(double rotationNoiseParam) {
        this.rotationNoiseParam = rotationNoiseParam;
    }

    public double getLaserNoiseParam() {
        return laserNoiseParam;
    }

    public void setLaserNoiseParam(double laserNoiseParam) {
        this.laserNoiseParam = laserNoiseParam;
    }


    public void setSigma(int sigma) {
        this.sigma = sigma;
    }

    public int getSigma() {
        return sigma;
    }
}
